package kvraft

import (
	"crypto/rand"
	"go-kvraft/rpcutil"
	"gopkg.in/yaml.v3"
	"io/ioutil"
	"math/big"
	"os"
)

// 读取ClientConfig配置
type ClientConfig struct {
	ClientEnd []struct {
		Ip   string
		Port string
	} `yaml:"servers"`
}

type KVClient struct {
	servers []*rpcutil.ClientEnd // 可用的RPC服务器列表
	// You will have to modify this struct.
	leader    int   // 当前Leader的编号
	clientId  int64 // 客户端的唯一标识符，每个客户端都有一个不同的clientId
	messageId int   // 当前请求的消息编号，每个请求都有一个不同的messageId
}

// 这个函数的实现方式可以确保生成的随机数分布均匀，并且不容易被预测和重现。
// 可以用来生成一个随机的clientId作为客户端的唯一标识符，从而避免多个客户端之间的冲突。
func nrand() int64 {
	max := big.NewInt(int64(1) << 62)     // 定义一个max常量，其值为 2^{62}，即最大的64位整数的一半
	bigx, _ := rand.Int(rand.Reader, max) // 使用rand.Reader生成一个64位整数bigx，其值在0和max-1之间
	x := bigx.Int64()                     // 将bigx转换为一个int64类型的整数x，返回x作为函数的结果
	return x
}

// MakeClerk 创建一个新的Clerk结构体
func MakeKVClient(servers []*rpcutil.ClientEnd) *KVClient {
	ck := new(KVClient)
	ck.servers = servers
	// You'll have to add code here.
	ck.leader = 0
	ck.clientId = nrand()
	ck.messageId = 1
	return ck
}

// Operation 函数用于向KVServer发送请求，获取或修改指定key的value值
func (ck *KVClient) Operation(key, value, method string) string {
	index := ck.leader
	for {
		// 构造请求
		request := OperationRequest{
			ClientId:  ck.clientId,  // 客户端的唯一标识符
			MessageId: ck.messageId, // 当前请求的消息编号
			Method:    method,       // 请求的方法类型
			Key:       key,          // 请求的key
			Value:     value,        // 请求的value
		}
		// 构造响应
		response := OperationResponse{}

		// 向KVServer发送请求
		ok := ck.servers[index%len(ck.servers)].Call("KVServer.Operation", &request, &response)
		err := response.Err
		value := response.Value

		// 如果请求成功，返回value
		if ok && err == OK {
			ck.leader = index % len(ck.servers) // 更新当前Leader的编号
			ck.messageId += 1                   // 更新当前请求的消息编号
			return value
		} else {
			index += 1 // 如果请求失败，尝试下一个服务器
		}
	}
}

// Get 函数用于向KVServer发送Get请求，获取指定key的value值
func (ck *KVClient) Get(key string) string {
	return ck.Operation(key, "", "Get")
}

// Put 函数用于向KVServer发送Put请求，修改指定key的value值
func (ck *KVClient) Put(key string, value string) {
	ck.Operation(key, value, "Put")
}

// Append 函数用于向KVServer发送Append请求，在指定key的value值后面追加新的value值
func (ck *KVClient) Append(key string, value string) {
	ck.Operation(key, value, "Append")
}

// 获取客户端通信实例
func GetClientEnds(path string) []*rpcutil.ClientEnd {
	config := getClientConfig(path)
	num := len(config.ClientEnd)
	if (num&1) == 0 || num < 3 {
		panic("the number of servers must be odd and greater than or equal to 3")
	}

	clientEnds := make([]*rpcutil.ClientEnd, 0)
	for _, end := range config.ClientEnd {
		address := end.Ip + ":" + end.Port
		client := rpcutil.TryConnect(address)

		ce := &rpcutil.ClientEnd{
			Addr:   address,
			Client: client,
		}

		clientEnds = append(clientEnds, ce)
	}
	return clientEnds
}

func getClientConfig(path string) *ClientConfig {
	if len(os.Args) == 2 {
		path = os.Args[1]
	}

	cfgFile, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}

	config := &ClientConfig{}
	err = yaml.Unmarshal(cfgFile, config)
	if err != nil {
		panic(err)
	}
	return config
}
